#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>	/* printk() */

#include <linux/types.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
struct mem_pool
{
	char *pdata;
	struct mem_pool *next;
};
typedef struct 
{
	dev_t dev_no;
	struct cdev cdev;
	struct file_operations *fops;
	unsigned int dev_count;
	char *name;
	struct mem_pool *pmem;
	unsigned int single_mem_size;
}mem_cdev_t;

int mem_open(struct inode *inode, struct file *filp)
{
	mem_cdev_t *dev;
	printk(KERN_INFO "mem_open\n");
	dev = container_of(inode->i_cdev, mem_cdev_t, cdev);
	filp->private_data = dev;
	
	dev->pmem = (struct mem_pool*)kmalloc(sizeof(struct mem_pool), GFP_KERNEL);

	dev->pmem->pdata = (char *)kmalloc(dev->single_mem_size, GFP_KERNEL);
	dev->pmem->next = NULL;
	return 0;
}
ssize_t mem_read(struct file *filp, char __user *buff, ssize_t count, loff_t *offp)
{
	mem_cdev_t *dev;
	printk(KERN_INFO "mem_read,offp:%d\n", *offp);
	dev = (mem_cdev_t*)filp->private_data;
	copy_to_user(buff+*offp,dev->pmem->pdata, count);
//	*offp += count;
	return count;
}
ssize_t mem_write(struct file *filp, const char __user *buff, ssize_t count, loff_t * offp)
{
	mem_cdev_t *dev;
	printk(KERN_INFO "mem_write,offp:%d\n", *offp);
	dev = (mem_cdev_t*)filp->private_data;
	copy_from_user(dev->pmem->pdata+*offp,buff, count);

//	*offp += count;
	return count;
}
int mem_release(struct inode *inode, struct file *filp)
{
	mem_cdev_t *dev;
//	dev = container_of(inode->i_cdev, mem_cdev_t, cdev);
	dev = (mem_cdev_t*)filp->private_data;
	if(!dev->pmem || !dev->pmem->pdata)
	{
		printk(KERN_INFO "invalid pointer\n");
		return -1;
	}
	kfree(dev->pmem->pdata);
	kfree(dev->pmem);
	printk(KERN_INFO "mem_release\n");
	return 0;
}
struct file_operations fops=
{
	.owner = THIS_MODULE,
	.open = mem_open,
	.release = mem_release,
	.read = mem_read,
	.write = mem_write,
};

mem_cdev_t mem_cdev=
{
	.cdev.owner = THIS_MODULE,
	.fops = &fops,
	.name = "mem_dev",
	.dev_count = 1,
	.single_mem_size = 1000,
};


MODULE_AUTHOR("suser");
MODULE_LICENSE("Dual BSD/GPL");

static void __exit hello_exit(void)
{
	
	unregister_chrdev(MAJOR(mem_cdev.dev_no),mem_cdev.name);
	cdev_del(&mem_cdev.cdev);
	unregister_chrdev_region(mem_cdev.dev_no, 1);
	printk(KERN_ALERT "good bye\n");
}

static int __init hello_init(void)
{
	int ret = 0;
	if(alloc_chrdev_region(&mem_cdev.dev_no, 0, 1, mem_cdev.name))
	{
		printk(KERN_INFO "failed to alloc dev no.\n");
		goto dev_no_err_out;
	}
	else
	{
		printk(KERN_INFO "major:%d minor:%d\n",MAJOR(mem_cdev.dev_no),MINOR(mem_cdev.dev_no));
	}
	cdev_init(&mem_cdev.cdev, mem_cdev.fops);
	ret = cdev_add(&mem_cdev.cdev, mem_cdev.dev_no, 1);
	if (ret < 0)
	{
		printk(KERN_INFO "failed to add chrdev\n");
		goto cdev_add_err_out;
	}
	printk(KERN_ALERT "hello world\n");	

	return 0;
cdev_add_err_out:	
	cdev_del(&mem_cdev.cdev);
	printk(KERN_INFO "cdev_del_err\n");
dev_no_err_out:	
	unregister_chrdev_region(mem_cdev.dev_no, 1);
	printk(KERN_INFO "dev_no_err\n");
	return -1;
}


module_init(hello_init);
module_exit(hello_exit);
	
